﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Web;
using System.Web.Script.Serialization;

namespace FileCloud.NET
{
    /// <summary>
    /// API for http://filecloud.io/.
    /// API documentation: http://code.google.com/p/filecloud/
    /// </summary>
    public class FileCloudAPIWrapper
    {
        #region API functions

        /// <summary>
        /// Get the apikey by sending the username and password combination for an account via a SSL.
        /// </summary>
        /// <remarks>http://code.google.com/p/filecloud/wiki/FetchApikey</remarks>
        /// <param name="username">Username.</param>
        /// <param name="password">Password.</param>
        /// <returns>The apikey for this account.</returns>
        /// <exception cref="System.Net.WebException">Is thrown in case response contains errors.</exception>
        /// TODO Check urlEncoding
        public static string FetchApiKey(string username, string password)
        {
            if (String.IsNullOrEmpty(username))
                throw new ArgumentException("username is null or empty.", "username");
            if (String.IsNullOrEmpty(password))
                throw new ArgumentException("password is null or empty.", "password");

            string data = string.Format("username={0}&password={1}", username, password);
            byte[] dataStream = FormUpload.Encoding.GetBytes(data);

            var request = _CreateRequest(FETCH_API_KEY_URL, data);

            var response = _GetResponse(request);

            _CheckError(response);

            return response.akey;
        }

        /// <summary>
        /// Ping is the the most simple call in the filecloud.io API, 
        /// and it allows you to check whether the service is up.
        /// </summary>
        /// <remarks>http://code.google.com/p/filecloud/wiki/Ping</remarks>
        /// <returns></returns>
        /// <exception cref="System.Net.WebException">Is thrown in case response contains errors.</exception>
        public static string Ping()
        {
            var request = (HttpWebRequest)WebRequest.Create(PING_URL);

            var response = _GetResponse(request);

            _CheckError(response);

            return response.message;
        }

        /// <summary>
        /// Upload file to filecloud.io.
        /// </summary>
        /// <param name="filePath">Path to file which must be uploaded.</param>
        /// <param name="apiKey">Apikey of the account to which this file is to be added to </param>
        /// <remarks>http://code.google.com/p/filecloud/wiki/Uploading</remarks>
        /// <returns>Uploaded file url.</returns>
        /// <exception cref="System.Net.WebException">Is thrown in case response contains errors.</exception>
        public static string Uploading(string filepath, string apiKey)
        {
            if (String.IsNullOrEmpty(filepath))
                throw new ArgumentException("filepath is null or empty.", "filepath");

            var uploadUrl = _FetchUploadUrl();

            return _UploadFile(apiKey, uploadUrl, filepath);
        }

        /// <summary>
        /// Users with premium accounts can request download tickets via this 
        /// API calls, one can then download the from the download ticket URL.
        /// </summary>
        /// <remarks>http://code.google.com/p/filecloud/wiki/FetchDownloadUrl</remarks>
        /// <param name="apiKey">Apikey for a users account.</param>
        /// <param name="fileKey">The unique key for a file.</param>
        /// <returns>Download ticket URL</returns>
        /// <exception cref="System.Net.WebException">Is thrown in case response contains errors.</exception>
        public static string FetchDownloadUrl(string apiKey, string fileKey)
        {
            if (String.IsNullOrEmpty(apiKey))
                throw new ArgumentException("apiKey is null or empty.", "apiKey");
            if (String.IsNullOrEmpty(fileKey))
                throw new ArgumentException("fileKey is null or empty.", "fileKey");

            string data = string.Format("akey={0}&ukey={1}", apiKey, fileKey);

            var request = _CreateRequest(FETCH_UPLOAD_URL, data);

            var response = _GetResponse(request);

            _CheckError(response);

            return response.download_url;
        }

        /// <summary>
        /// Use this call to get details for a file, and also check of the file link is not dead 
        /// (and if it is dead get a reason why).
        /// </summary>
        /// <remarks>http://code.google.com/p/filecloud/wiki/FetchFileDetails</remarks>
        /// <param name="apiKey">Apikey for a users account.</param>
        /// <param name="fileKey">The unique key for a file.</param>
        /// <returns>Info about file.</returns>
        public static FileCloudAPIFileInfo FetchFileDetail(string apiKey, string fileKey)
        {
            if (String.IsNullOrEmpty(apiKey))
                throw new ArgumentException("apiKey is null or empty.", "apiKey");
            if (String.IsNullOrEmpty(fileKey))
                throw new ArgumentException("fileKey is null or empty.", "fileKey");

            string data = string.Format("akey={0}&ukey={1}", apiKey, fileKey);

            var request = _CreateRequest(FETCH_FILEDETAILS_URL, data);

            var response = _GetResponse(request);

            return new FileCloudAPIFileInfo(response);
        }

        #endregion

        #region Helpers

        /// <summary>
        /// Determine upload url.
        /// </summary>
        /// <remarks>http://code.google.com/p/filecloud/wiki/Uploading#Step1_-_Determine_upload_url</remarks>
        private static string _FetchUploadUrl()
        {
            var request = (HttpWebRequest)WebRequest.Create(FETCH_UPLOAD_URL);

            var response = _GetResponse(request);

            _CheckError(response);

            return response.upload_url;
        }

        /// <summary>
        /// Upload file.
        /// </summary>
        /// <remarks>http://code.google.com/p/filecloud/wiki/Uploading#Step2_-_Upload_file</remarks>
        /// <returns>File's ukey.</returns>
        private static string _UploadFile(string apiKey, string uploadUrl, string filepath)
        {
            Debug.Assert(!String.IsNullOrEmpty(uploadUrl), "uploadUrl is null or empty.");
            Debug.Assert(!String.IsNullOrEmpty(filepath), "filepath is null or empty.");

            // Create request.
            var parameters = new Dictionary<string, object>();

            // Apikey is optional parameter.
            if (apiKey != null)
                parameters.Add("akey", apiKey);

            parameters.Add("Filedata", new FileParameter(filepath));
            var request = FormUpload.CreateMultipartFormDataPostRequest(uploadUrl, parameters);

            // Get response
            var response = _GetResponse(request);

            _CheckError(response);

            return response.ukey;
        }

        /// <summary>
        /// Check that response has no error - otherwise throw exception.
        /// </summary>
        /// <param name="response">Response.</param>
        /// <exception cref="System.Net.WebException">Is thrown in case response contains errors.</exception>
        private static void _CheckError(dynamic response)
        {
            if (response.status == "error")
            {
                throw new WebException(response.message);
            }
        }

        /// <summary>
        /// Create simple post request.
        /// </summary>
        private static HttpWebRequest _CreateRequest(string requestUrl, string data)
        {
            Debug.Assert(!String.IsNullOrEmpty(requestUrl), "requestUrl is null or empty.");
            Debug.Assert(!String.IsNullOrEmpty(data), "data is null or empty.");

            var request = (HttpWebRequest)WebRequest.Create(requestUrl);
            request.Timeout = int.MaxValue;
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";

            byte[] dataStream = FormUpload.Encoding.GetBytes(data);
            request.ContentLength = dataStream.Length;
            using (var stream = request.GetRequestStream())
            {
                stream.Write(dataStream, 0, dataStream.Length);
            }
            return request;
        }

        /// <summary>
        /// Get response from request as dynamic.
        /// </summary>
        private static dynamic _GetResponse(HttpWebRequest request)
        {
            Debug.Assert(request != null, "request is null.");

            using (var response = request.GetResponse())
            {
                using (var stream = response.GetResponseStream())
                {
                    var reader = new StreamReader(stream);
                    var responseString = reader.ReadToEnd();

                    var serializer = new JavaScriptSerializer();
                    serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
                    return serializer.Deserialize(responseString, typeof(object));
                }
            }
        }

        #endregion

        #region Request Strings

        private const string FETCH_API_KEY_URL = @"https://secure.filecloud.io/api-fetch_apikey.api";

        private const string API_URL_FORMAT = @"http://api.filecloud.io/api-{0}.api";
        private static readonly string PING_URL = string.Format(API_URL_FORMAT, "ping");
        private static readonly string FETCH_UPLOAD_URL = string.Format(API_URL_FORMAT, @"fetch_upload_url");
        private static readonly string FETCH_DOWNLOAD_URL = string.Format(API_URL_FORMAT, @"fetch_download_url");
        private  static readonly string FETCH_FILEDETAILS_URL = string.Format(API_URL_FORMAT, @"fetch_file_details");

        #endregion
    }
}
